package actions;

import actions.BasicAction;
import flixel.util.FlxPoint;
import flixel.tweens.FlxTween;
import flixel.tweens.FlxEase;
import utils.Frame;

/**
 * Representa una acción que puede contener otras acciones.
 */

 /// TODO: falta actualizar las posiciones y dibujar el frame
class BasicContainer extends BasicAction
{
	public var parent: BasicContainer;
	public var alphaMultiplier: Float = 1;

	public function new(Parent: BasicContainer = null)
	{
		super();
		parent = Parent;
		_actions = new Array<BasicAction>();
		_frame = new Frame(this, "assets/img/frame2");
		_minSize = new FlxPoint(64 + 2, 8); // el +2 es por padding
		setSize(_minSize.x, _minSize.y);
	}


	/**
	 * Reinicia la ejecución
	 */
	public function startExecution()
	{
		_executionIndex = 0;
		for (action in _actions)
		{
			if(Type.getClass(action) == BasicContainer)
			{
				cast(action, BasicContainer).startExecution();
			}
		}
	}


	/**
	 * Devuelve si hay una acción siguiente a la actual en el contenedor
	 */
	public function hasNext(): Bool
	{
		// hay una acción siguiente si el indice no llegó al final, o si está en el último elemento y el
		// ultimo elemento es un contenedor que tiene acciones pendientes...
		return _executionIndex < _actions.length || (_executionIndex == _actions.length && Type.getClass(_actions[_actions.length -1]) == BasicContainer && cast(_actions[_actions.length -1], BasicContainer).hasNext());
	}

	/**
	 * Devuelve la próxima acción para la ejecución (acción, no contenedor), si la hay, o null
	 */
	public function getNext(): ExecutableAction
	{
		if(Type.getClass(_actions[_executionIndex]) == actions.ExecutableAction)
		{
			return cast(_actions[_executionIndex++], actions.ExecutableAction);
		}
		else
		{
			var container = cast(_actions[_executionIndex], BasicContainer);
			if(container.hasNext())
			{
				return container.getNext();
			}else{
				++_executionIndex;
				return getNext();
			}
		}
	}


	public override function update()
	{
		// para los tweens
		for( action in _actions)
		{
			action.update();
		}
		_frame.update();
	}

	/**
	 * Devuelve la cantidad de acciones en el contenedor
	 */
	public function getCount(): Int
	{
		return _actions.length;
	}

	/**
	 * Dibuja las acciones dentro del contenedor
	 */
	public override function draw(): Void
	{
		_frame.draw();
		for( action in _actions)
		{
			action.draw();
		}
	}

	/**
	 * Llamada cuando sabemos que hay solapamiento y necesitamos
	 * hacer lugar para una nueva acción
	 */
	public function makeSpaceFor(a: BasicAction): Void
	{
		rearrange(a);
	}

	public function removeSpaceFor(a: BasicAction): Void
	{
		rearrange(null);
	}

	/**
	 * Calcula en que ordinal dentro del contenedor
	 * debería ir una acción arrastrada
	 */
	private function calculateActionPosition(DraggedAction: BasicAction): Int
	{
		// determinar si la acción va arriba o abajo de una acción, 
		// o adentro, si es adentro y hay solapamiento con la acción y la acción es un contenedor
		// entonces
		// hacer llamada recursiva
		if(_actions.length == 0 || DraggedAction.y+DraggedAction.height/2.0<=_actions[0].y+_actions[0].height/2.0) return 0;

		for(i in 0..._actions.length-1)
		{
			if(DraggedAction.y+DraggedAction.height/2.0 >= _actions[i].y+_actions[i].height/2.0 && DraggedAction.y+DraggedAction.height/2.0 < _actions[i+1].y+_actions[i].height/2.0)
			{
				return i+1;
			}
		}
		return _actions.length;
	}


	/**
	 * Acomoda cada acción en su lugar y calcula el tamaño del contenedor
	 */
	private function rearrange(DraggedAction: BasicAction = null)
	{
		var newOrdinal: Int = -1;
		if(DraggedAction != null)
		{
			newOrdinal = calculateActionPosition(DraggedAction);
			//trace("ordinal de la arrast: "+newOrdinal);
		}

		var ordinal: Int = 0;

		
		var accumHeight: Float = Defs.CONTAINER_VPADDING;
		var w: Float = _minSize.x;
		
		if(newOrdinal == 0)
		{
			accumHeight += DraggedAction.height + Defs.CONTAINER_VPADDING;
			ordinal++;
		}

		for(a in _actions)
		{
			if(ordinal == newOrdinal)
			{
				accumHeight += DraggedAction.height + Defs.CONTAINER_VPADDING;
				trace("adding height: "+DraggedAction.height );
				ordinal++;
			}

			a.positionInsideParent =  ordinal;
			if(a.width > w)
			{
				w = a.width;
			}
			a.tweenedMove(x + Defs.CONTAINER_HPADDING, y + accumHeight);
			//a.x = x + Defs.CONTAINER_HPADDING;
			//a.y = y + accumHeight;
			accumHeight += a.height + Defs.CONTAINER_VPADDING;
			ordinal++;
		}

		// si la acción arrastrada va al final
		if(newOrdinal == _actions.length && _actions.length != 0)
		{
			accumHeight += DraggedAction.height + Defs.CONTAINER_VPADDING;
		}

		if(accumHeight<_minSize.y)
		{
			accumHeight = _minSize.y;
		}

		tweenedResize(w, accumHeight );
	}


	public function drop(DraggedAction: BasicAction)
	{
		/// TODO: se puede evitar llamar tanto a calculateActionPosition()???
		_actions.insert(calculateActionPosition(DraggedAction), DraggedAction);
		DraggedAction.alpha = alpha * alphaMultiplier;
		rearrange();
	}

	public function tweenedResize(_w: Float, _h: Float){
		// si ya está en posición o está haciendo tweening, no hace nada
		if ((width == _w && height == _h) || _isTweeningSize ) return;
		else{
			var options:TweenOptions = { type: FlxTween.ONESHOT, ease: TWEENED_RESIZE_EASING, complete: onTweenedResizeEnd}
			
			_isTweeningSize = true;
			_sizeTween= FlxTween.tween(this, {"width": _w, "height": _h}, TWEENED_RESIZE_DURATION, options);
		}
	}

	/**
	 * Borra una acción del contenerdor y la devuelve
	 */
	public function pick(position: FlxPoint): BasicAction
	{
		for(a in _actions)
		{
			if(a.overlapsPoint(position))
			{
				var pickedAction = a;
				_actions.remove(a);
				return pickedAction;
			}
		}
		return null;
	}

	private function onTweenedResizeEnd(Tween: FlxTween)
	{
		_isTweeningSize = false;
	}


	public override function set_alpha(Alpha: Float): Float
	{
		if(_frame != null)
			_frame.alpha = Alpha * alphaMultiplier;

		if(_actions != null)
		{
			for(a in _actions)
			{
				a.alpha = Alpha * alphaMultiplier;
			}
		}


		alpha = Alpha * alphaMultiplier;
		return Alpha;
	}

	public function getCurrentAction(): BasicAction
	{
		if(_executionIndex <= 0 || _executionIndex> _actions.length)
		{
			return null;
		}
		else
		{
			return _actions[_executionIndex-1];
		}
	}

	private var _actions: Array<BasicAction>;
	private var _executionIndex: Int; 			// cuando iteramos sobre las acciones, contiene el indice de la accion actual
	private var _frame: Frame;					// el frame del contenedor
	private var _minSize: FlxPoint;				// tamaño mínimo que puede tener el contenedor
	private var _sizeTween: FlxTween;
	private var _isTweeningSize: Bool = false;
	private static inline var TWEENED_RESIZE_DURATION: Float = 0.1;
	private static var TWEENED_RESIZE_EASING: EaseFunction = FlxEase.quadIn;
}